//
// Created by zy-li14 on 16-9-24.
//

#include "json/json.h"
#include "Game/Game.h"
#include "sdk/PlayerInfo.h"
#include <fstream>
#include <include/sdk/misc.h>
#include <Entities/plant.h>
#include <iostream>
#include <tuple>
#include <exception>
#include <algorithm>
#include <include/sdk/stats.h>

using namespace std;

Game::Game():entityManager (this)
{
    entityManager.clear();
    init();
}

void Game::oneRoundRun()
{
    for (auto& i: entityManager.entityList) if (i.second->properties["dead"] != "true")
        for (auto& j: i.second->skillList)
            if (j.second->hasPrefix("_Automatic_")){
                i.second->useLater(j.second->name, {});
            }
    for (int turn = int(TurnName::StartTurn); turn != int(TurnName::TotalTurns); ++ turn){
        std::queue<EntitySkillCall>& queue = this->SkillCallQueue[turn];
        while (!queue.empty()){
            Entity* sender;
            std::string skillName;
            std::vector<std::string> params;
            std::tie(sender, skillName, params) = queue.front();
            queue.pop();
            //sender->skillList[skillName]->use(this, params);
            sender->useSkill(skillName, params);
        }
    }
    for (int turn = int(TurnName::StartTurn); turn != int(TurnName::TotalTurns); ++ turn)
        this->SkillCallQueue.clear();
    writeReplay();
    PerformedSkills.clear();
    return;
}

PlayerInfo Game::getPlayerInfo(int playerId) const {
    Json::Value root = replay_data[nowRound];
    root["playerId"] = playerId;
    root["rand_seed"] = rand();
    PlayerInfo info;
    info.status = jsonToString(root);
    return info;
}

void Game::roundPreparation(const Commands &cmd) {
    nowRound ++;
    std::vector<EntitySkillCall> skillCall;
	for(const Command &o: cmd.cmds)
	{
        // name: UseSkill
        // params: entityId skillName skillParams...
        if(o.name == "UseSkill")
        {
            const std::vector<std::string> &params = o.params;
            if(params.size() < 2)
                continue;
            std::string entityId = params[0];
            std::string skillName = params[1];
            std::vector<std::string> skillParams = std::vector<std::string>(params.begin() + 2, params.end());
            Entity *origin = this->entityManager.findEntityByID(entityId);
            if(origin == nullptr)
                continue;
            if(!origin->hasSkill(skillName)){
                //std::cout << "Skill " << skillName << " not exists." << std::endl;
                continue;
            }
            skillCall.push_back(make_tuple(origin, skillName, skillParams));
        }
	}
    if (nowRound % 2 == 1){
        stable_sort(skillCall.begin(),skillCall.end(),[](const EntitySkillCall& a, const EntitySkillCall& b){
           return (stoi((get<0>(a))->properties["team"]) < stoi((get<0>(b))->properties["team"]));
        });
    }
    else {
        stable_sort(skillCall.begin(),skillCall.end(),[](const EntitySkillCall& a, const EntitySkillCall& b){
            return (stoi((get<0>(a))->properties["team"]) > stoi((get<0>(b))->properties["team"]));
        });
    }
    for (auto i : skillCall){
        Entity* sender;
        std::string skillName;
        std::vector<std::string> params;
        std::tie(sender, skillName, params) = i;
        sender->useLater(skillName, params);
    }
}

void Game::init() {
    replay_data.clear();
    nowRound = 0;
    linkManager = entityManager.get<entity::EnergyManager>();
    writeReplay();
}

void Game::initPlant(){
	//把两个东西放远一点
    auto plant1 = entityManager.get<entity::BasePlant>();
    plant1->setPos(Point(TEAM1_POS_X, TEAM1_POS_Y));
    plant1->properties["team"] = std::to_string(1);
    plant1->useSkill("ReCalculateStats", {});
    auto plant2 = entityManager.get<entity::BasePlant>();
    plant2->setPos(Point(TEAM2_POS_X, TEAM2_POS_Y));
    plant2->properties["team"] = std::to_string(2);
    plant2->useSkill("ReCalculateStats", {});
    setupBattleGround();
}

void Game::writeReplay(){
    Json::Value root;
    for (auto i : entityManager.entityList) if (!i.second->hidden && i.second->properties["dead"] != "true")
        root["objects"].append(i.second->toJson() );
    for (auto i : PerformedSkills){
        Json::Value action;
        action["sender"] = std::get<0>(i);
        action["action"] = std::get<1>(i);
        for (auto j: std::get<2>(i))
            action["params"].append(j);
        root["actions"].append(action);
    }
    root["round"] = this->nowRound;
    replay_data.push_back(root);
}

Json::Value Game::getReplay(){
    Json::Value root;
    root["winner"] = getWinner();
    for (auto i: replay_data)
        root["rounds"].append(i);
    return root;
}

void Game::dumpReplay(std::string filename) {
    Json::Value root = getReplay();
    std::ofstream ofs("./replay_" + filename + ".json");
    ofs << root.toStyledString();
    ofs.close();
}

int Game::getWinner() const
{
    if (nowRound == MAX_ROUND){
        return this->getWinnerFinal();
    }
    int entityCount[2] = {0, 0};
    for (auto i : entityManager.entityList) if (i.second->hasPrefix("_Actor_") ){
        int team = std::stoi(i.second->properties["team"]);
        if (1 <= team && team <= 2 && i.second->properties["dead"] != "true"){
            entityCount[team - 1] ++;
        }
    }
    if (entityCount[0] == 0 && entityCount[1] == 0) return TIE;
    if (entityCount[0] == 0) return 2;
    if (entityCount[1] == 0) return 1;
    return UNFINISHED;
}
//TODO: Finish this.
void Game::roundClean(){}

void Game::setupBattleGround() {
    replay_data.clear();
    writeReplay();
}

int Game::getWinnerFinal() const{
    vector<int> sum = {0, 0};
    for (auto i:entityManager.entityList) {
        Actor* entity = dynamic_cast<Actor*>(i.second);
        if (entity != nullptr && entity->properties["dead"] != "true"){
			try
			{
				int team = stoi(entity->properties["team"]) - 1;
				sum[team] += stats::getLevelUPConsumption(stoi(entity->properties["level"])) - stats::getLevelUPConsumption(0);
				sum[team] += stoi(entity->properties["energy"]);
			}
			catch(std::exception)
			{
			}
        }
    }
    if (sum[0] > sum[1])
        return 1;
    else if (sum[0] < sum[1])
        return 2;
    else
        return TIE;
}
